#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <types.h>
#include <utils.h>
#include <unistd.h>
#include <syserr.h>
#include <device.h>
#define _LIST_H_
#include <chn.h>
#include "dlist.h"

#define CHK_DEV(chn) do {   \
    if (!chn->dev)          \
    {                       \
        CHN_PE("%s: please identify device for me\n", chn_name(chn));\
        return -EIO;        \
    }                       \
} while (0)

struct channel *chn_get(const char *name)
{
    return (struct channel *)obj_find_with_type(OBJ_CHN, name);
}
struct channel *chn_get_with_type(u8 type, u8 no)
{
    struct object *iter;

    list_for_each_entry(iter, obj_head(OBJ_CHN), entry)
    {
        struct channel *chn = list_entry(iter, struct channel, obj);

        if (chn->type == type && chn->no == no)
            return chn;
    }
    return NULL;
}

void chn_init(struct channel *chn, const char *name, u8 type, u8 no)
{
    INIT_LIST_HEAD(&chn->subchn);
    chn->type = type;
    chn->no   = no;
    subject_init(&chn->status_change_event);
    subject_init(&chn->frame_ack_event);
    obj_init(&chn->obj, OBJ_CHN, name);
}

int chn_add(struct channel *chn, const char *name, u8 type, u8 no)
{
    chn_init(chn, name, type, no);
    return obj_add(&chn->obj, OBJ_CHN, name);
}

void chn_del(struct channel *chn)
{
    chn_close(chn);
    obj_del((object_t *)chn);
}

static int do_rcv_data(device_t *dev)
{
    int ret, dlen;
    struct channel *chn = dev_to_chn(dev);

    if (!chn->ops || !chn->ops->recv_data)
        return -1;

    ret = device_ctrl(dev, DEV_CTRL_CMD_GET_FIFO_LEN, &dlen);
    if (ret < 0) dlen = 0;

    ret = chn->ops->recv_data(chn);
    if (ret < 0)
    {
        /* something error, clear device cache
         */
        if (dlen > 0)
            chn_read(chn, NULL, dlen);
        soft_timer_stop(&chn->frame_timeout_tmr);
        return ret;
    }

    /* no cache */
    if (dlen == 0) return 0;

    /* user may reset device.
     */
    if (dev_status(dev) != DEVICE_STATUS_CONNECTED)
    {
        soft_timer_stop(&chn->frame_timeout_tmr);
        return 0;
    }

    chn_read(chn, NULL, ret);
    dlen -= ret;

    /* still has data
     */
    if (dlen > 0)
    {
        if (chn->frame_to == 0)
        {
            chn_read(chn, NULL, dlen);
            return 0;
        }

        if (ret > 0 || !ev_is_active(&chn->frame_timeout_tmr))
            soft_timer_start(&chn->frame_timeout_tmr, chn->frame_to, 0);
    }
    else
    {
        soft_timer_stop(&chn->frame_timeout_tmr);
    }
    return 0;
}

static int usr_ev_cb(device_t *dev, enum DEVICE_EVENT e, const void *args)
{
    struct channel *chn = dev_to_chn(dev);
    const struct channel_operations *ops = chn->ops;

    switch (e)
    {
    case DE_CONN:
        if (ops && ops->on_conn)
            ops->on_conn(chn, (dev_conn_evt_args_t *)args);
        break;
    case DE_DISCONN:
        if (ops && ops->on_disconn)
            ops->on_disconn(chn, (dev_conn_evt_args_t *)args);
        break;
    case DE_RCV:
        return do_rcv_data(dev);
    default:
        break;
    }
    return 0;
}
const struct usr_cb_ops dev_usr_cb_ops =
{
    .ev_cb = usr_ev_cb,
};

void chn_attach(struct channel *chn, struct channel *newchn)
{
    list_add(&newchn->subchn, &chn->subchn);
}
void chn_detach(struct channel *chn)
{
    list_del_init(&chn->subchn);
}

int chn_cfg_set(struct channel *chn)
{
    int ret;
    device_t *dev = chn->dev;

    if (chn->ops && chn->ops->get_cfg)
    {
        struct dev_cfg cfg;
        ret = device_ctrl(dev, DEV_CTRL_CMD_GET_CFG, &cfg);
        if (ret < 0) return ret;
        chn->ops->get_cfg(chn, &cfg);
        ret = device_ctrl(dev, DEV_CTRL_CMD_SET_CFG, &cfg);
        if (ret < 0) return ret;
    }
    return 0;
}
static int chn_dev_setup(struct channel *chn, ev_loop_t *loop, u32 flag)
{
    int ret;
    device_t *dev;

    if (!chn->dev && (chn->ops && chn->ops->find_dev))
        chn->dev = chn->ops->find_dev(chn);
    assert(chn->dev);

    dev = chn->dev;
    if (dev_status(dev) != DEVICE_STATUS_IDLE)
        return -EALREADY;

    ret = chn_cfg_set(chn);
    if (ret < 0) return ret;

    if (chn->rxfifosize > 0)
        device_ctrl(dev, DEV_CTRL_CMD_SET_RXFIFO_SIZE, &chn->rxfifosize);
    if (chn->txfifosize > 0)
        device_ctrl(dev, DEV_CTRL_CMD_SET_TXFIFO_SIZE, &chn->txfifosize);

    if (chn->frame_bytes_to > 0)
    {
        ret = device_ctrl(dev, DEV_CTRL_CMD_SET_FRAME_TO, &chn->frame_bytes_to);
        if (ret < 0) return ret;
    }

    device_set_cb(dev, &dev_usr_cb_ops);
    dev->usr_data = chn;
    dev->autoopen = chn->autoopen;

    return device_open(dev, loop, flag);
}

static void chn_ack_timer_cb(void *data)
{
    struct channel *chn = data;

    CHN_PI("<%s>: restart channel in 1 second, "
           "reason[No communication within %d seconds]!\n",
           chn_name(chn), chn->no_ack_to / HZ);
    chn_restart(chn, 1);
}
static void chn_frame_timeout_cb(void *data)
{
    struct channel *chn = data;

    CHN_PI("<%s>: channel frame timeout!, drop 1 byte and redecode...\n", chn_name(chn));
    chn_read(chn, NULL, 1);

    usr_ev_cb(chn->dev, DE_RCV, NULL);
}
int chn_setup(struct channel *chn, ev_loop_t *loop, u32 flag)
{
    int ret;
    const struct channel_operations *ops = chn->ops;

    CHN_PI("<%s>: channel setup ...\n", chn_name(chn));

    if (chn->obj.active)
    {
        CHN_PE("<%s>: channel already active\n", chn_name(chn));
        return -EALREADY;
    }

    chn->loop = loop;
    if (ops && ops->setup)
    {
        ret = ops->setup(chn);
        if (ret < 0) return ret;
    }

    ret = chn_dev_setup(chn, loop, flag);
    if (ret < 0)
    {
        switch (ret)
        {
        case -EALREADY:
        case -EINPROGRESS:
            ret = 0;
            break;
        default:
            CHN_PE("<%s>: device_setup failed!\n", chn_name(chn));
            if (ops && ops->close) ops->close(chn);
            return ret;
        }
    }

    if (chn->no_ack_to > 0)
    {
        ret = soft_timer_add(loop, &chn->no_ack_tmr, "no_ack_tmr", chn_ack_timer_cb, chn);
        assert(ret == 0);
        soft_timer_start(&chn->no_ack_tmr, chn->no_ack_to, chn->no_ack_to);
    }

    if (chn->frame_to > 0)
    {
        ret = soft_timer_add(loop, &chn->frame_timeout_tmr, "frame_timeout", chn_frame_timeout_cb, chn);
        assert(ret == 0);
    }

    chn->obj.active = 1;
    CHN_PI("<%s>: channel setup done!\n", chn_name(chn));
    return ret;
}


void chn_close_subchn(struct channel *chn)
{
    struct channel *subchn;

    while (!list_empty(&chn->subchn))
    {
        subchn = list_first_entry(&chn->subchn, struct channel, subchn);

        chn_detach(subchn);
        chn_close(subchn);
    }
}
void chn_close(struct channel *chn)
{
    const struct channel_operations *ops = chn->ops;

    if (!chn->obj.active)
        return;

    assert(chn->dev);
    device_close(chn->dev);

    chn_close_subchn(chn);

    if (chn->no_ack_to > 0)
        soft_timer_del(&chn->no_ack_tmr);

    if (chn->frame_to > 0)
        soft_timer_del(&chn->frame_timeout_tmr);

    if (ops && ops->close)
        ops->close(chn);

    chn->obj.active = 0;
    CHN_PI("<%s>: channel close done!\n", chn_name(chn));
}

void chn_get_frame(struct channel *chn, struct proto_buffer *pb)
{
    subject_notify(&chn->frame_ack_event, pb);

    if (chn->no_ack_to > 0)
        soft_timer_start(&chn->no_ack_tmr, chn->no_ack_to, 0);
}

int chn_read(struct channel *chn, void *buffer, size_t size)
{
    int ret;

    CHK_DEV(chn);

    ret = device_read(chn->dev, 0, buffer, size);
    if (ret > 0 && chn->prframe)
    {
        if (buffer)
        {
            CHN_PD("<%s>: read %d bytes: ", chn_name(chn), ret);
            CHN_PDARR(buffer, ret);
        }
        else
        {
            CHN_PD("<%s>: clear %d bytes!\n", chn_name(chn), ret);
        }
    }
    return ret;
}
int chn_peek(struct channel *chn, void *buffer, size_t size)
{
    int ret;

    CHK_DEV(chn);

    ret = device_peek(chn->dev, 0, buffer, size);
    if (ret > 0 && chn->prframe)
    {
        CHN_PD("<%s>: peek %d bytes: ", chn_name(chn), ret);
        CHN_PDARR(buffer, ret);
    }
    return ret;
}
int chn_write(struct channel *chn, const void *buffer, size_t size)
{
    const struct channel_operations *ops = chn->ops;

    CHK_DEV(chn);

    if (chn->prframe)
    {
        CHN_PD("<%s>: write %zu bytes: ", chn_name(chn), size);
        CHN_PDARR(buffer, size);
    }

    if (ops && ops->write)
        return ops->write(chn, buffer, size);
    return device_write(chn->dev, 0, buffer, size);
}
int chn_write_reliably(struct channel *chn, const void *buffer, size_t size)
{
    const struct channel_operations *ops = chn->ops;

    CHK_DEV(chn);

    if (ops && ops->write_reliably)
        return ops->write_reliably(chn, buffer, size);
    return 0;
}

int chn_status(struct channel *chn)
{
    u32 status = DEVICE_STATUS_IDLE;

    if (chn->obj.active)
    {
        assert(chn->dev);
        device_ctrl(chn->dev, DEV_CTRL_CMD_GET_STATUS, &status);
    }
    return status;
}

int chn_restart(struct channel *chn, ubase_t delay)
{
    const struct channel_operations *ops = chn->ops;

    if (!delay) delay = 30;

    delay *= HZ;

    if (ops && ops->restart_hook)
        ops->restart_hook(chn, delay);
    return device_ctrl(chn->dev, DEV_CTRL_CMD_RESTART, (void *)delay);
}

int chn_add_proto(struct channel *chn, const struct object *prot)
{
    int i;

    for (i = 0; i < array_size(chn->prot); i++)
    {
        if (!chn->prot[i])
        {
            chn->prot[i] = prot;
            return 0;
        }
    }
    return -1;
}
void chn_del_proto(struct channel *chn, const struct object *prot)
{
    int i;

    for (i = 0; i < array_size(chn->prot); i++)
    {
        if (chn->prot[i] == prot)
        {
            chn->prot[i] = NULL;
            return;
        }
    }
}


int chn_add_app(struct channel *chn, struct object *app)
{
    int i;

    for (i = 0; i < array_size(chn->apps); i++)
    {
        if (!chn->apps[i])
        {
            chn->apps[i] = app;
            return 0;
        }
    }
    return -1;
}
void chn_del_app(struct channel *chn, struct object *app)
{
    int i;

    for (i = 0; i < array_size(chn->apps); i++)
    {
        if (chn->apps[i] == app)
        {
            chn->apps[i] = NULL;
            return;
        }
    }
}

struct object *chn_get_app(struct channel *chn, const char *name)
{
    int i;

    for (i = 0; i < array_size(chn->apps); i++)
    {
        if (chn->apps[i] && !strcmp(chn->apps[i]->name, name))
            return chn->apps[i];
    }
    return NULL;
}
